package computer;

import javax.swing.*;
import java.awt.*;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Stack;

public class MyCalculator extends JFrame {
    //操作值栈
    private Stack<Double> expressionValue = new Stack<>();
    //操作符栈
    private Stack<String> expressionOperator = new Stack<>();


    // 构造方法
    public MyCalculator() throws HeadlessException {
        // 设置窗口属性
        this.setTitle("我的計算器😊");
        this.setSize(400, 500);    // 设置窗口大小
        this.setLocationRelativeTo(null);    // 设置窗口居中显示
        //獲取容器並設置布局
        Container c = getContentPane();
        c.setLayout(null);

        // 输入面板
        JTextArea jt = new JTextArea(100, 100); //創建文本框
        jt.setFont(new Font("Aria", Font.BOLD, 30));    // 设置字体
        jt.setLineWrap(true); // 自动换行
        JScrollPane sp = new JScrollPane(jt); // 加入滚动条
        sp.setBounds(0, 0, 385, 163);   // 设置滚动条位置
        c.add(sp);    // 添加滚动条到容器中

        // 按钮面板
        String[] buttons = {
                "(", ")", "CE", "C",
                "7", "8", "9", "+",
                "4", "5", "6", "-",
                "1", "2", "3", "×",
                "0", ".", "=", "÷"};

        JButton[] jbs = new JButton[buttons.length];
        for (int i = 0; i < jbs.length; i++) {
            jbs[i] = new JButton(buttons[i]);
            jbs[i].setFont(new Font("Aria", Font.BOLD, 20));    // 设置字体
            jbs[i].setBounds((i % 4) * 100, 162 + (i / 4) * 60, 100, 60);    // 设置按钮位置
            c.add(jbs[i]);  // 添加按钮到容器中
        }

        // 監聽除=,CE,C按鈕外的按鈕事件
        for (int i = 0; i < 20; i++) {
            final int index = i;
            if (index != 2 && index != 3 && index != 18) {
                jbs[i].addActionListener(e -> {
                    // 按下按鈕後，將按鈕文字加入到文本框中
                    String buttonText = buttons[index];
                    System.out.print(buttonText);
                    jt.append(buttonText);
                });
            }
        }

        // 監聽CE按鈕事件
        jbs[2].addActionListener(e -> {
            //清除輸入面板中的所以内容
            jt.setText("");
            expressionValue.clear();
            expressionOperator.clear();
        });

        // 監聽C按鈕事件
        jbs[3].addActionListener(e -> {
            //清除輸入面板中的所以内容
            jt.setText(jt.getText().substring(0, jt.getText().length() - 1));
        });

        // 監聽=按鈕事件
        jbs[18].addActionListener(e -> {
            try {
                System.out.print("=");
                // 計算並將結果加入到文本框中
                String text = jt.getText() + "#";
                //調用calculate方法計算
                double result = calculate(text);
                jt.setText(""); // 清除輸入面板中的所以内容
                jt.append(String.valueOf(result));  // 將結果加入到文本框中
                System.out.println(result);
            } catch (Exception ex) {
                jt.setText(""); // 清除輸入面板中的所以内容
                jt.append("Error"); // 將錯誤訊息加入到文本框中
            }
        });

        // 窗口可見
        setVisible(true);
        // 窗口關閉時關閉JVM
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    // 壓入棧中
    private double calculate(String text) {
        //優先順序添加到hashmap中
        HashMap<String, Integer> operationPriority = new HashMap<>();
        operationPriority.put("+", 1);
        operationPriority.put("-", 1);
        operationPriority.put("×", 2);
        operationPriority.put("÷", 2);
        operationPriority.put("(", 0);
        operationPriority.put(")", 0);
        //將結束標記符#優先權設為0
        operationPriority.put("#", 0);

        //將#壓入操作符栈
        expressionOperator.push("#");

        //是否壓入操作數棧標志,如果i!=flag,壓入操作數棧
        int flag = 0;

        //遍歷文本串
        for (int i = 0; i < text.length(); i++) {
            String s = String.valueOf(text.charAt(i));  // 取出單個字符
            //字符是否為數字或小數點
            if (!(s.matches("[0-9.]"))) {
                if (flag != i) {
                    //將操作數壓入操作數棧,儅操作數為小數時可以把它當成一個整體
                    expressionValue.push(Double.parseDouble(text.substring(flag, i)));
                }
                flag = i + 1;    // 設置flag為下一個字符的位置

                //#表示已經到棧底了
                //當前字符不是#且操作符棧頂不是#時執行while循環,否則直接return棧中的數(最終的結果)
                while (!(s.equals("#") && expressionOperator.peek().equals("#"))) {
                    //當前字符的優先權是否高於操作符棧頂的優先權,或當前字符是左括號
                    if (operationPriority.get(s) > operationPriority.get(expressionOperator.peek()) || s.equals("(")) {
                        expressionOperator.push(s); //壓入操作符棧
                        break;  //跳出while循環,即跳到for循環遍歷文本串処
                    } else {
                        if (s.equals(")")) {
                            //當前字符是右括號時,循環執行calculate()計算,直到彈出的棧頂元素是左括號"("為止
                            while (!expressionOperator.peek().equals("(")) {
                                calculate();
                            }
                            //彈出"("
                            expressionOperator.pop();
                            break; //跳出while循環,即跳到for循環遍歷文本串処
                        }
                        /*儅for//遍歷文本串已經遍歷到了"#"時,表示已經遍歷完了,此時expressionValue棧中只有兩個數
                        expressionOperator中只有一個操作符和棧底元素"#"
                        最後一次執行calculate()計算,並將結果壓入expressionValue棧中,此時expressionValue棧中只有一個數
                        所以return expressionValue.pop()即為結果
                         */
                        calculate();
                    }
                }
            }
        }
        return expressionValue.pop();
    }

    // 計算
    private void calculate() {
        // 取出操作數和操作符
        double num1 = expressionValue.pop();
        double num2 = expressionValue.pop();
        String operator = expressionOperator.pop();

        // 計算並將結果壓入操作數棧中
        double result = 0;
        switch (operator) {
            case "+":
                result = num2 + num1;
                expressionValue.push(result);
                break;
            case "-":
                //解決相減時由於double精度限制參數精度丟失
                BigDecimal bigDecimal = new BigDecimal(String.valueOf(num2)).subtract(BigDecimal.valueOf(new BigDecimal(String.valueOf(num1)).doubleValue()));
                result = bigDecimal.doubleValue();
                expressionValue.push(result);
                break;
            case "×":
                result = num2 * num1;
                expressionValue.push(result);
                break;
            case "÷":
                if (num1 == 0) {
                    throw new ArithmeticException("除數不能為0");
                }
                result = num2 / num1;
                expressionValue.push(result);
                break;
            default:
                break;
        }
    }

    public static void main(String[] args) {
        new MyCalculator();
    }
}
